﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Bling.DSL;
using Bling.WPF;
using Bling.Util;
using Bling.Physics;


namespace Bling.PhysicsTutorial {
  /// <summary>
  /// Initial physics example. Use physics to apply momentum and constraints to slider thumbs. I know this isn't 
  /// very useful, but its nice and easy. 
  /// </summary>
  public class Sliders : PhysicsCanvas {
    public Sliders() : base("Sliders", 10) { }
    protected override Action Init(CanvasBl MainCanvas, World World) {
      PointBl P = new PointBl(0, 10);
      // Create a plain physics block of sliders by specifying number of sliders and a function to create each slider.
      PlainBlock<Slider, SliderBl> block = new PlainBlock<Slider, SliderBl>(World, 20, i => {
        var slider = new SliderBl(MainCanvas) {
          Minimum = 0, Maximum = 1, Value = 0,
          Width = MainCanvas.Width, LeftTop = P,
        };
        slider.Background = i.SelectColor() * slider.Value;
        P = slider.LeftBottom;
        return slider;
      }) { Name = "Sliders", };
      // Create a physical value property since we can't do physics directly
      // on the slider's own value dependency property. 
      var ValueProperty = block.NewBodyProperty<double>("Value");
      // A simply fucntion that eases access to the physical value property on a physical body. 
      Func<PlainBlock<Slider, SliderBl>.Body, DoubleBl> Value = (body) => body.GetProperty(ValueProperty);

      // When mix is pressed, the slider values are randomized
      var Mix = new ButtonBl(MainCanvas) {
        Bottom = MainCanvas.Height, Left = 0,
        Content = "Random",
      };
      // Used to control the strength of the physical constraints that are keeping the slider thumbs in order.
      var Strength = new LabelSliderBl(MainCanvas) {
        Minimum = 0, Maximum = 1, Value = 1,
        Width = MainCanvas.Width - Mix.Width, Bottom = MainCanvas.Height,
        Left = Mix.Right, LabelName = "Strength",
      }.Value;

      // Physical constraints are usually expressed on each body in the entire block, use ForAll to do that.
      block.ForAll(body => {
        // link the physical value property with the slider's value dependency property.
        // Update the Value dependency property from the physical property when the mouse is not over the slider, otherwise
        // do the reverse (assign the dependency property to the physical property).
        // Basically, a way of interacting with the physical system.
        Value(body).Link[!body.At.IsMouseOver] = body.At.Value;
        // Apply momentum to physical slider values using verlet, with a loss (friction) of 2%. 
        Value(body).Step = (now, old) => now.VerletStep(0.02, old);
        // Apply constraints 3 times to increase solver accuracy.
        for (int i = 0; i < 3; i++) {
          // This physical value should be greater than the previous one, use Max on the previous one to do that.
          // TIP: body.Prev(N) returns the body at index N before this one, with rollover (e.g., body.Prev[1] on index 0
          //      will return the last body).
          // Use relax to express a strength and guard for the constraint. .5 is 50% which is additionally weakened
          // by the strength slider. body.Index > 0 ensures that the constraint is not enforced on the first slider.
          Value(body).Relax[.5 * Strength, body.Index > 0] = Value(body).Max(Value(body.Prev[1]));
          // A constraint between two points that both move must be split into two: the forward and backward constraint, 
          // both at most 50% strength. This constraint is the sibling of the previous constraint, and works 
          // on the next slider while the previous constraint one works on the previous sliders.          
          Value(body).Relax[.5 * Strength, body.Index < block.Count - 1] = Value(body).Min(Value(body.Next[1]));
        }
        // Specify restitution, since the old value will not be very accurate if the value his the edge (0 or 1).
        Value(body).Old = Value(body).Old.Restitution(Value(body), 0, 1);
        // Finally, clamp the current physical slider vlaue. 
        Value(body).Bind = Value(body).Clamp(0, 1);
      });
      // return an action that gets executed after code is generated, in which case the value of a physical property 
      // can be set directly. In this case, we need to set the physical slider value's directly to implement the
      // Mix button's randomization functionality. 
      return () => {

        // Creates a random double value that returns a new double each time evaluated.
        DoubleBl random = new Random();

        // Create a list of actions that will randomize each slider's physical value.
        Action[] setValues = new Action[block.Count];
     /*first method:   
        for (int i = 0; i < block.Count; i++)
          // MakeNow will create an action that performs an assignment to the physical value from
          // another value. The action is compiled via the DLR and will execute very quickly. 
          setValues[i] = DoubleBl.Assign(Value(block[i]), random);
      */
        // ... or we could create one action parameterized by an index!
        Action<int> setValue0 = DoubleBl.Assign<int>(i => Value(block[i]), i => random);

        Mix.Click = () => {
          // perform the actions. 
          //foreach (var a in setValues) a();  //for first method
          for (int i = 0; i < block.Count; i++) setValue0(i);
        };
      };
    }
  }
}
